package com.hmall.gateway.routers;


import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;

/**
 * 动态路由加载器
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicRouteLoader {

    private final NacosConfigManager nacosConfigManager;
    private final RouteDefinitionWriter writer;

    // 路由配置的DataId
    private final String dataId = "gateway-routes.json";
    // 路由配置的Group
    private final String group = "DEFAULT_GROUP";
    // 超时时间
    private final long timeout = 5000;
    // 存储路由id的set
    private final Set<String> routeIds = new HashSet<>();


    /**
     * 初始化路由配置监听器：此方法会在Bean初始化完成后执行
     */
    @PostConstruct
    public void initRouteConfigListener() throws NacosException {
        // 1. 项目启动时，加载一次路由配置
        String configInfo = nacosConfigManager.getConfigService()
                .getConfigAndSignListener(
                        dataId,
                        group,
                        timeout,
                        new Listener() {
                            @Override
                            public void receiveConfigInfo(String configInfo) {
                                // 2. 监听到路由变化时，需要更新路由配置表
                                updateRouteConfig(configInfo);
                            }

                            @Override
                            public Executor getExecutor() {
                                return null;
                            }
                        }
                );
        // 3. 第一次加载路由配置的时候，也需要更新路由配置表
        updateRouteConfig(configInfo);
    }

    /**
     * 更新路由配置表
     *
     * @param configInfo 路由配置信息
     */
    private void updateRouteConfig(String configInfo) {
        log.info("更新路由配置表开始：{}", configInfo);
        // 1. 解析路由配置信息，转换为RouteDefinition对象
        List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);

        // 2. 删除已有的路由
        routeIds.forEach(routeId -> {
            // delete方法是异步的，需要订阅才能生效
            writer.delete(Mono.just(routeId)).subscribe();
        });
        // 清空路由id
        routeIds.clear();

        // 3. 更新路由配置表
        routeDefinitions.forEach(routeDefinition -> {
            // 3.1 更新路由
            // save方法是异步的，需要订阅才能生效
            // save不能直接传入RouteDefinition对象，需要使用Mono.just()包装一下
            writer.save(Mono.just(routeDefinition)).subscribe();
            // 3.2 记录路由id 方便第二次更新时删除
            routeIds.add(routeDefinition.getId());
        });
        log.info("更新路由配置表结束：{}", configInfo);
    }
}
